package net.cathackers.devel.scmp.chat.impl.xmpp.iqcomponent.roster;

import java.util.Iterator;
import java.util.LinkedList;

import net.cathackers.devel.scmp.auth.impl.jabber.JabberUserID;
import net.cathackers.devel.scmp.auth.interfaces.UserID;
import net.cathackers.devel.scmp.chat.exceptions.RosterItemAlreadyExistsException;
import net.cathackers.devel.scmp.chat.exceptions.RosterItemNotFoundException;
import net.cathackers.devel.scmp.chat.interfaces.ChatManager;
import net.cathackers.devel.scmp.chat.interfaces.ChatSession;
import net.cathackers.devel.scmp.chat.interfaces.RosterBackend;
import net.cathackers.devel.scmp.chat.interfaces.RosterSubscriptionManager;
import net.cathackers.devel.scmp.chat.interfaces.RosterSubscriptionManager.Ask;
import net.cathackers.devel.scmp.chat.interfaces.RosterSubscriptionManager.Subscription;
import net.cathackers.devel.scmp.exceptions.BackendException;
import net.cathackers.devel.scmp.factories.SCMPFactory;
import net.cathackers.devel.scmp.xmpp.IQPacket;
import net.cathackers.devel.scmp.xmpp.IQType;
import net.cathackers.devel.scmp.xmpp.Packet;
import net.cathackers.devel.scmp.xmpp.PresencePacket;
import net.cathackers.devel.scmp.xmpp.RosterPacket;
import net.cathackers.devel.scmp.xmpp.StanzaErrors;
import net.cathackers.devel.scmp.xmpp.PresencePacket.PresenceType;
import net.cathackers.devel.scmp.xmpp.StanzaErrors.Condition;

import org.apache.log4j.Logger;
import org.dom4j.Element;

public class RosterProcessor {
    private RosterBackend backend_;
    private RosterSubscriptionManager rosterManager_;
    private ChatManager chatManager_;
    private Logger logger_;

    public RosterProcessor() {
	backend_ = SCMPFactory.getRosterBackend();
	rosterManager_ = new RosterSubscriptionManagerImpl();
	chatManager_ = SCMPFactory.getChatManager();
	logger_ = SCMPFactory.getLogger(this);
    }

    /**
     * used to obtain a copy of the roster stored on the server
     * @throws RosterItemNotFoundException
     * @throws BackendException
     */
    public RosterPacket resetRoster(RosterPacket roster)
	    throws RosterItemNotFoundException, BackendException {
	RosterPacket ret = null;
	UserID rosterOwner = roster.getFrom();
	LinkedList<RosterItem> items = backend_.getRoster(rosterOwner);
	logger_.info("we get the roster from backend " + items.size());
	for (Iterator<RosterItem> i = items.iterator(); i.hasNext();) {
	    roster.addRosterItem(i.next());
	    logger_.info("getting new item from the Roster of " + rosterOwner.getID());
	}
  
	ChatSession session = chatManager_.getChatSession(roster.getBaseStreamID());
	 
	session.setRosterRequested(true);
	logger_.info("this user " + rosterOwner.getID()
		+ " request a copy of his roster");
	
	roster.switchDirection();
	roster.setIQType(IQType.result);

	ret = roster;
	return ret;
    }

    /**
     * used to update the roster stored on the server
     * @throws RosterItemAlreadyExistsException 
     * @throws BackendException 
     * @throws RosterItemNotFoundException 
     */
    public synchronized void updateRoster(RosterPacket roster) throws BackendException, RosterItemAlreadyExistsException, RosterItemNotFoundException  {
	logger_.debug("updating the backend with this roster " + roster.toXML());
	LinkedList<Element> items = roster.getAllItems();
	for (Iterator<Element> i = items.iterator(); i.hasNext();) {
	    Element item = i.next();
	    String jid = item.attributeValue("jid");
	    RosterItem rosterItem = roster.getRosterItem(item);
	  
	    boolean isASubscriber = rosterManager_.isASubscriber(roster
		    .getFrom(), new JabberUserID(jid));
	    logger_.debug("the Roster packet " + roster.toXML());
	    if (isASubscriber == false) {
		this.addNewItem(roster, rosterItem);
	    } else {
		logger_.info("we are going to update the backend");
		if (rosterItem.getSubscription().equals(Subscription.REMOVE)) {
		    logger_.info("we are going to remove an item");
		    this.removeItem(roster, rosterItem);
		} 
		else {
		    logger_.info("we are going to update this element");
		    this.updateItem(roster, rosterItem);
		}
            
	    }

	}
    }

    private void removeItem(RosterPacket roster, RosterItem rosterItem)
	    throws BackendException {
	UserID owner = roster.getFrom();
	UserID subscriber = rosterItem.getJID();
	backend_.removeRosterItem(owner, subscriber);
	logger_.info("remove " + subscriber.getUsername()
		+ " information from " + owner.getUsername() + "roster");
	PresencePacket unsubscribePresence = new PresencePacket(PresenceType.UNSUBSCRIBE);
	unsubscribePresence.setTo(subscriber.getGeneralUserID());
	unsubscribePresence.setFrom(owner.getGeneralUserID());
	unsubscribePresence.setBaseStreamID(roster.getBaseStreamID());
	
	//chatManager_.deliverToUser(unsubscribePresence);
	chatManager_.route(unsubscribePresence);
	
	PresencePacket unsubscribedPresence = new PresencePacket(PresenceType.UNSUBSCRIBED);
	unsubscribedPresence.setTo(subscriber.getGeneralUserID());
	unsubscribedPresence.setFrom(owner.getGeneralUserID());
	unsubscribedPresence.setBaseStreamID(roster.getBaseStreamID());
	//chatManager_.deliverToUser(unsubscribedPresence);
	chatManager_.route(unsubscribedPresence);
	
	PresencePacket unAvailablePresence = new PresencePacket(PresenceType.UNAVAILABLE);
	unAvailablePresence.setTo(subscriber.getGeneralUserID());
	unAvailablePresence.setFrom(owner);
	chatManager_.deliverToUser(unAvailablePresence);
	this.pushRoster(roster);


    }

    private void updateItem(RosterPacket roster, RosterItem rosterItem)
	    throws RosterItemNotFoundException, BackendException {
	
	logger_.debug("update the roster information in persistent storage");
	UserID owner;
	if (null == roster.getFrom())  {
	    owner = chatManager_.getChatSession(roster.getBaseStreamID()).getUserID();  
	} else {
	    owner = roster.getFrom();
	}
	UserID subscriber = rosterItem.getJID();
	logger_.debug("will query the db in a moment");
	RosterItem backendItem = backend_.getRosterItem(owner, subscriber);
	
	if (rosterItem.getSubscription().equals(Subscription.NONE) && rosterItem.getAsk().equals(Ask.NONE)) {
	    if (backendItem.getSubscription().equals(Subscription.FROM)) {
		rosterItem.setSubscription(Subscription.FROM);  
	        rosterItem.setNickname(rosterItem.getJID().getID());
	    }
	}
	if (!rosterItem.getSubscription().equals(Subscription.NONE)) {
	    backend_.updateSubscription(owner, subscriber, rosterItem
		    .getSubscription());
	    logger_.info("update subscription of " + subscriber.getUsername()
		    + " to " + rosterItem.getSubscription().toString());
	}
	if (rosterItem.getAsk().equals(backendItem.getAsk()) == false) {
	    backend_.updateAsk(owner, subscriber, rosterItem.getAsk());
	    logger_.info("update ask of " + subscriber.getUsername() + " to "
		    + rosterItem.getAsk().toString());
	}
	if (rosterItem.getNickname() != null) {
	    backend_
		    .updateNickname(owner, subscriber, rosterItem.getNickname());
	    logger_.info("update nickName of " + subscriber.getUsername()
		    + " to " + rosterItem.getNickname());
	}
	if (rosterItem.getGroup() != null) {
	    backend_.updateGroup(owner, subscriber, rosterItem.getGroup());
	    logger_.info("update group of " + subscriber.getUsername() + " to "
		    + rosterItem.getGroup());
	}
	logger_.debug("Pushing roster...");
	this.pushRoster(roster);
	

    }

    /**
     * add new subscriber to user roster
     * 
     * @param roster
     * @param rosterItem
     * @throws BackendException
     * @throws RosterItemAlreadyExistsException
     */
    private void addNewItem(RosterPacket roster, RosterItem rosterItem)
	    throws BackendException, RosterItemAlreadyExistsException {
	Subscription sub = rosterItem.getSubscription();
	if (sub.equals(Subscription.REMOVE)) {
	    return;
	}
	
	logger_.info("add new item to " + roster.getFrom().getUsername()
		+ " roster ");
	backend_.addRosterItem(roster.getFrom(), rosterItem);

	if (!rosterItem.getSubscription().equals(Subscription.FROM) && !rosterItem.getAsk().equals(Ask.NONE))
	this.pushRoster(roster);
    }

    /**
     * Asynchronous roster updates sent from the server to active resources that
     * have requested the roster
     */

    public void pushRoster(RosterPacket roster) {
	roster.switchDirection();
	boolean isOnline = chatManager_.checkIfSessionOnline(roster.getTo().getGeneralUserID());
	if (isOnline) {
	    chatManager_.deliverToUser(roster);
	}
	logger_.debug("Roster was pushed");

    }

    public Packet getReply(UserID from, String id) {
	IQPacket result = new IQPacket(IQType.result);
	result.setID(id);
        result.setTo(from);
	logger_.debug("roster reply is " + result.toXML());
	return result;
    }

    public Packet getErrorPacket(RosterPacket roster, Condition condition) {
	StanzaErrors error = new StanzaErrors(condition);
	roster.setError(error);
	roster.switchDirection();
	roster.setIQType(IQType.error);
	return roster;
    }
}
